feat: 設定画面にプライバシーポリシー・利用規約・サポート導線を追加#53
Conversation
- url_launcher / in_app_review パッケージを追加 - 定数ファイル(app_constants.dart)でURL・メールアドレスを一元管理 - URL起動ユーティリティ(url_launcher_helper.dart)を追加 - 設定画面を3セクション6メニュー項目に拡充 - アプリ情報: バージョン情報・ライセンス情報 - 法的情報: プライバシーポリシー・利用規約 - サポート: お問い合わせ(端末情報自動付与)・レビューを書く - テストにセクション・メニュー項目の表示確認を追加 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
設定画面(Settings)に「法的情報(プライバシーポリシー/利用規約)」と「サポート(問い合わせ/レビュー)」の導線を追加し、外部URL起動・アプリ内レビュー依頼を行えるようにするPRです。book_manager の設定タブを拡充して、ストア公開に必要な情報導線を整備する目的に合致しています。
Changes:
- 設定画面を3セクション(アプリ情報/法的情報/サポート)構成に拡充し、外部リンク・メール・レビュー導線を追加
- URL/メール起動ヘルパーと、URL/メール/ストアURL等の定数ファイルを新規追加
url_launcher/in_app_review追加に伴う依存関係更新と、表示テスト追加
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/screens/settings_screen.dart | 設定画面に法的情報/サポートのメニューを追加し、URL起動・メール起動・レビュー依頼を実装 |
| lib/utils/url_launcher_helper.dart | url_launcher を使った外部URL/メール起動の共通ヘルパーを追加 |
| lib/constants/app_constants.dart | プライバシーポリシー/利用規約/サポートメール/ストアURLの定数を追加 |
| test/widget_test.dart | 設定画面のセクション/メニュー項目表示のテストを追加・更新 |
| pubspec.yaml | url_launcher / in_app_review をdependenciesに追加 |
| pubspec.lock | 依存関係解決結果の更新(関連パッケージ追加、SDK制約行の更新含む) |
| macos/Flutter/GeneratedPluginRegistrant.swift | 追加プラグインの自動登録更新 |
| linux/flutter/generated_plugins.cmake | 追加プラグインの自動登録更新 |
| linux/flutter/generated_plugin_registrant.cc | 追加プラグインの自動登録更新 |
| windows/flutter/generated_plugins.cmake | 追加プラグインの自動登録更新 |
| windows/flutter/generated_plugin_registrant.cc | 追加プラグインの自動登録更新 |
| import 'dart:async'; | ||
| import 'dart:io'; |
There was a problem hiding this comment.
dart:io の import と Platform.* の参照が入っているため、Web ターゲット(web/ がある構成)ではコンパイルエラーになります。Web もサポートする場合は kIsWeb / defaultTargetPlatform を使う、もしくは条件付き import で Web では Platform を参照しない実装に切り替えてください。
There was a problem hiding this comment.
修正しました(cafd7e1)。dart:io の Platform を package:flutter/foundation.dart の defaultTargetPlatform / kIsWeb に変更し、Webターゲットでもコンパイルエラーが発生しないようにしました。
| if (context.mounted) { | ||
| final storeUrl = Platform.isIOS | ||
| ? AppConstants.appStoreUrl | ||
| : AppConstants.googlePlayUrl; | ||
| await launchExternalUrl(context, storeUrl); | ||
| } |
There was a problem hiding this comment.
ストアURLのフォールバックが Platform.isIOS 以外はすべて Google Play URL になっており、Windows/macOS/Linux/Web では誤った遷移になります。Android/iOS 以外は「未対応」のSnackBar表示にするか、プラットフォームごとの適切な遷移先(例: 公式サイト)を用意して分岐してください。
| if (context.mounted) { | |
| final storeUrl = Platform.isIOS | |
| ? AppConstants.appStoreUrl | |
| : AppConstants.googlePlayUrl; | |
| await launchExternalUrl(context, storeUrl); | |
| } | |
| if (!context.mounted) { | |
| return; | |
| } | |
| String? storeUrl; | |
| if (Platform.isIOS) { | |
| storeUrl = AppConstants.appStoreUrl; | |
| } else if (Platform.isAndroid) { | |
| storeUrl = AppConstants.googlePlayUrl; | |
| } else { | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| const SnackBar( | |
| content: Text('このプラットフォームではレビュー機能は未対応です。'), | |
| ), | |
| ); | |
| return; | |
| } | |
| await launchExternalUrl(context, storeUrl); |
There was a problem hiding this comment.
修正しました(cafd7e1)。defaultTargetPlatform で Android/iOS のみストアURLに分岐し、それ以外のプラットフォームでは「このプラットフォームではレビュー機能は未対応です」の SnackBar を表示するようにしました。
| to: AppConstants.supportEmail, | ||
| subject: '【読書管理】お問い合わせ', | ||
| body: '\n\n---\n' | ||
| 'アプリバージョン: ${version ?? "不明"}\n' |
There was a problem hiding this comment.
prefer_single_quotes: true のlint設定に反して、文字列リテラルにダブルクォート("不明")が使われています。シングルクォートに統一してください(flutter analyze で警告/エラーになります)。
| 'アプリバージョン: ${version ?? "不明"}\n' | |
| 'アプリバージョン: ${version ?? '不明'}\n' |
There was a problem hiding this comment.
修正しました(cafd7e1)。シングルクォートに統一しました。
| Future<void> launchExternalUrl(BuildContext context, String url) async { | ||
| final uri = Uri.parse(url); | ||
| if (await canLaunchUrl(uri)) { | ||
| await launchUrl(uri, mode: LaunchMode.externalApplication); | ||
| } else { |
There was a problem hiding this comment.
canLaunchUrl を前提にしているため、Android 11+ の package visibility 制約下では Manifest の <queries> 設定が無いと canLaunchUrl が false になり、実際は起動できるのに SnackBar 側に倒れる可能性があります。launchUrl の戻り値(bool)を確認して失敗時に SnackBar を出す方式に寄せるか、canLaunchUrl を使い続けるなら必要な <queries> を追加してください。
There was a problem hiding this comment.
修正しました(cafd7e1)。canLaunchUrl による事前チェックを廃止し、launchUrl の戻り値(bool)で成否を判定する方式に変更しました。これにより Android 11+ の package visibility 制約下でも正しく動作します。
| /// | ||
| /// 失敗時は [context] を使って SnackBar でエラーを表示する。 | ||
| Future<void> launchExternalUrl(BuildContext context, String url) async { | ||
| final uri = Uri.parse(url); |
There was a problem hiding this comment.
Uri.parse(url) は不正なURL文字列で FormatException を投げてクラッシュする可能性があります(定数差し替えミス等)。パース失敗を try/catch で吸収して SnackBar を出すなど、例外経路もユーザー向けにハンドリングしてください。
| final uri = Uri.parse(url); | |
| late final Uri uri; | |
| try { | |
| uri = Uri.parse(url); | |
| } on FormatException { | |
| if (context.mounted) { | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| const SnackBar(content: Text('不正なURLです')), | |
| ); | |
| } | |
| return; | |
| } |
There was a problem hiding this comment.
修正しました(cafd7e1)。Uri.parse を try/catch で囲み、FormatException 発生時は「不正なURLです」の SnackBar を表示して早期リターンするようにしました。
| // TODO: リリース前に実際のURLに差し替えること | ||
| /// プライバシーポリシーURL | ||
| static const privacyPolicyUrl = | ||
| 'https://example.com/privacy-policy'; | ||
|
|
||
| /// 利用規約URL | ||
| static const termsOfServiceUrl = | ||
| 'https://example.com/terms-of-service'; | ||
|
|
||
| /// お問い合わせメールアドレス | ||
| static const supportEmail = 'support@example.com'; | ||
|
|
||
| /// App Store URL(iOS) | ||
| static const appStoreUrl = | ||
| 'https://apps.apple.com/app/idXXXXXXXXXX'; | ||
|
|
||
| /// Google Play URL(Android) | ||
| static const googlePlayUrl = | ||
| 'https://play.google.com/store/apps/details?id=com.example.book_manager'; |
There was a problem hiding this comment.
プレースホルダーのURL/メール(example.com や idXXXXXXXXXX)がそのままアプリUIから到達可能になっています。リリース前差し替え前提なら、少なくとも本番向けビルドでは実URLが必須になるようにビルドフレーバー/環境変数で注入するか、未設定時はメニューを無効化して「準備中」表示にするなど、誤誘導を防いでください。
| // TODO: リリース前に実際のURLに差し替えること | |
| /// プライバシーポリシーURL | |
| static const privacyPolicyUrl = | |
| 'https://example.com/privacy-policy'; | |
| /// 利用規約URL | |
| static const termsOfServiceUrl = | |
| 'https://example.com/terms-of-service'; | |
| /// お問い合わせメールアドレス | |
| static const supportEmail = 'support@example.com'; | |
| /// App Store URL(iOS) | |
| static const appStoreUrl = | |
| 'https://apps.apple.com/app/idXXXXXXXXXX'; | |
| /// Google Play URL(Android) | |
| static const googlePlayUrl = | |
| 'https://play.google.com/store/apps/details?id=com.example.book_manager'; | |
| /// プライバシーポリシーURL | |
| /// | |
| /// - 本番ビルドでは `--dart-define=PRIVACY_POLICY_URL=...` で必ず設定すること | |
| /// - 未設定(空文字)の場合は、画面遷移やリンク表示を無効化し「準備中」などを表示すること | |
| static const String privacyPolicyUrl = String.fromEnvironment( | |
| 'PRIVACY_POLICY_URL', | |
| defaultValue: '', | |
| ); | |
| /// 利用規約URL | |
| /// | |
| /// - 本番ビルドでは `--dart-define=TERMS_OF_SERVICE_URL=...` で必ず設定すること | |
| /// - 未設定(空文字)の場合は、画面遷移やリンク表示を無効化し「準備中」などを表示すること | |
| static const String termsOfServiceUrl = String.fromEnvironment( | |
| 'TERMS_OF_SERVICE_URL', | |
| defaultValue: '', | |
| ); | |
| /// お問い合わせメールアドレス | |
| /// | |
| /// - 本番ビルドでは `--dart-define=SUPPORT_EMAIL=...` で必ず設定すること | |
| /// - 未設定(空文字)の場合は、問い合わせメニューを無効化し「準備中」などを表示すること | |
| static const String supportEmail = String.fromEnvironment( | |
| 'SUPPORT_EMAIL', | |
| defaultValue: '', | |
| ); | |
| /// App Store URL(iOS) | |
| /// | |
| /// - 本番ビルドでは `--dart-define=APP_STORE_URL=...` で必ず設定すること | |
| /// - 未設定(空文字)の場合は、ストア遷移ボタンを非表示または無効化すること | |
| static const String appStoreUrl = String.fromEnvironment( | |
| 'APP_STORE_URL', | |
| defaultValue: '', | |
| ); | |
| /// Google Play URL(Android) | |
| /// | |
| /// - 本番ビルドでは `--dart-define=GOOGLE_PLAY_URL=...` で必ず設定すること | |
| /// - 未設定(空文字)の場合は、ストア遷移ボタンを非表示または無効化すること | |
| static const String googlePlayUrl = String.fromEnvironment( | |
| 'GOOGLE_PLAY_URL', | |
| defaultValue: '', | |
| ); |
There was a problem hiding this comment.
プライバシーポリシー・利用規約のURLは GitHub Pages(https://haino357.github.io/book_manager/...)に更新済みです(b3fc960)。ストアURL・メールアドレスについてはリリース前に差し替えが必要なため、TODOコメントを残しています。--dart-define による環境変数注入はリリースビルド整備時に別Issueで対応する方針とします。
- docs/にプライバシーポリシー・利用規約のHTMLページを作成 - app_constants.dartのURLをGitHub PagesのURLに更新 - アプリから外部ブラウザで法的文書を表示する方式に統一 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- dart:ioのPlatformをfoundationのdefaultTargetPlatform/kIsWebに変更しWeb対応 - レビュー機能のストアURL分岐をAndroid/iOSのみに限定し他プラットフォームは未対応表示 - ダブルクォートをシングルクォートに統一 - canLaunchUrlをlaunchUrlの戻り値判定に変更しAndroid 11+の誤判定を回避 - Uri.parseにtry/catchを追加し不正URL時のクラッシュを防止 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| final inAppReview = InAppReview.instance; | ||
| if (await inAppReview.isAvailable()) { | ||
| await inAppReview.requestReview(); | ||
| } else { | ||
| if (!context.mounted) { | ||
| return; | ||
| } | ||
|
|
||
| String? storeUrl; | ||
| if (defaultTargetPlatform == TargetPlatform.iOS) { | ||
| storeUrl = AppConstants.appStoreUrl; | ||
| } else if (defaultTargetPlatform == TargetPlatform.android) { | ||
| storeUrl = AppConstants.googlePlayUrl; | ||
| } | ||
|
|
||
| if (storeUrl != null) { | ||
| await launchExternalUrl(context, storeUrl); | ||
| } else { | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| const SnackBar( | ||
| content: Text('このプラットフォームではレビュー機能は未対応です'), | ||
| ), | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
in_app_review は Windows/Linux/Web ではプラグインが登録されていないため、isAvailable() 呼び出しが MissingPluginException 等で落ちる可能性があります(このファイルではプラットフォーム判定より前に呼んでいます)。defaultTargetPlatform で Android/iOS のときだけ InAppReview を呼ぶ、もしくは isAvailable/requestReview を try/catch して失敗時にストアURLフォールバック/未対応SnackBarに倒すようにしてください。
| final inAppReview = InAppReview.instance; | |
| if (await inAppReview.isAvailable()) { | |
| await inAppReview.requestReview(); | |
| } else { | |
| if (!context.mounted) { | |
| return; | |
| } | |
| String? storeUrl; | |
| if (defaultTargetPlatform == TargetPlatform.iOS) { | |
| storeUrl = AppConstants.appStoreUrl; | |
| } else if (defaultTargetPlatform == TargetPlatform.android) { | |
| storeUrl = AppConstants.googlePlayUrl; | |
| } | |
| if (storeUrl != null) { | |
| await launchExternalUrl(context, storeUrl); | |
| } else { | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| const SnackBar( | |
| content: Text('このプラットフォームではレビュー機能は未対応です'), | |
| ), | |
| ); | |
| } | |
| } | |
| // Web/非対応プラットフォームではプラグインが登録されていない可能性があるため、先に判定 | |
| if (kIsWeb || | |
| (defaultTargetPlatform != TargetPlatform.android && | |
| defaultTargetPlatform != TargetPlatform.iOS)) { | |
| if (!context.mounted) { | |
| return; | |
| } | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| const SnackBar( | |
| content: Text('このプラットフォームではレビュー機能は未対応です'), | |
| ), | |
| ); | |
| return; | |
| } | |
| try { | |
| final inAppReview = InAppReview.instance; | |
| if (await inAppReview.isAvailable()) { | |
| await inAppReview.requestReview(); | |
| return; | |
| } | |
| } catch (_) { | |
| // プラグイン未登録やその他の例外時はストアURLフォールバックへ | |
| } | |
| if (!context.mounted) { | |
| return; | |
| } | |
| String? storeUrl; | |
| if (defaultTargetPlatform == TargetPlatform.iOS) { | |
| storeUrl = AppConstants.appStoreUrl; | |
| } else if (defaultTargetPlatform == TargetPlatform.android) { | |
| storeUrl = AppConstants.googlePlayUrl; | |
| } | |
| if (storeUrl != null) { | |
| await launchExternalUrl(context, storeUrl); | |
| } else { | |
| ScaffoldMessenger.of(context).showSnackBar( | |
| const SnackBar( | |
| content: Text('このプラットフォームではレビュー機能は未対応です'), | |
| ), | |
| ); | |
| } |
| package_info_plus: ^9.0.0 | ||
| url_launcher: ^6.3.1 | ||
| in_app_review: ^2.0.10 |
There was a problem hiding this comment.
pubspec.lock 側の Flutter SDK 制約が >=3.35.0 に上がっていますが、pubspec.yaml の environment:flutter は >=3.32.0 のままです。このままだと 3.32 系で pub get が再現できず依存解決が壊れるので、pubspec.yaml の Flutter 制約も >=3.35.0 に合わせるか、依存バージョンを調整して 3.32 を維持するかをどちらかに統一してください。
| static const supportEmail = 'support@example.com'; | ||
|
|
||
| /// App Store URL(iOS) | ||
| static const appStoreUrl = | ||
| 'https://apps.apple.com/app/idXXXXXXXXXX'; | ||
|
|
||
| /// Google Play URL(Android) | ||
| static const googlePlayUrl = | ||
| 'https://play.google.com/store/apps/details?id=com.example.book_manager'; |
There was a problem hiding this comment.
supportEmail / appStoreUrl / googlePlayUrl がプレースホルダーのままなので、ユーザーが到達すると誤誘導(example.com へのメール作成や無効なストアURL)になります。少なくとも未設定・プレースホルダー時は該当メニューを無効化して「準備中」表示にする、もしくは --dart-define 等で本番ビルド時に必須注入する仕組みにして、実値が入るまで起動しない/導線が出ないようにしてください。
| static const supportEmail = 'support@example.com'; | |
| /// App Store URL(iOS) | |
| static const appStoreUrl = | |
| 'https://apps.apple.com/app/idXXXXXXXXXX'; | |
| /// Google Play URL(Android) | |
| static const googlePlayUrl = | |
| 'https://play.google.com/store/apps/details?id=com.example.book_manager'; | |
| /// 空文字の場合は「準備中」として扱い、問い合わせ導線を表示しないこと | |
| static const supportEmail = ''; | |
| /// App Store URL(iOS) | |
| /// 空文字の場合は「準備中」として扱い、ストア導線を表示しないこと | |
| static const appStoreUrl = ''; | |
| /// Google Play URL(Android) | |
| /// 空文字の場合は「準備中」として扱い、ストア導線を表示しないこと | |
| static const googlePlayUrl = ''; |
- in_app_review 呼び出しを Web/非対応プラットフォーム判定 + try/catch で囲み、MissingPluginException を吸収 - pubspec.yaml の Flutter SDK 制約を pubspec.lock に合わせて >=3.35.0 に統一 - supportEmail/appStoreUrl/googlePlayUrl をプレースホルダーから空文字に変更し、 お問い合わせメニューは空文字時に「準備中」表示で無効化、 レビュー導線は空文字時にストアURLフォールバックを行わずSnackBar表示
| <p>本プライバシーポリシーは、必要に応じて更新されることがあります。変更があった場合は、アプリのアップデートを通じてお知らせします。</p> | ||
|
|
||
| <h2>お問い合わせ</h2> | ||
| <p>本プライバシーポリシーに関するお問い合わせは、アプリ内の「お問い合わせ」からご連絡ください。</p> |
| <p>開発者は、必要に応じて本規約を変更することがあります。変更後の利用規約は、アプリのアップデートを通じて公開されます。アップデート後のアプリの利用をもって、変更後の規約に同意したものとみなします。</p> | ||
|
|
||
| <h2>お問い合わせ</h2> | ||
| <p>本規約に関するお問い合わせは、アプリ内の「お問い合わせ」からご連絡ください。</p> |
| final storeUrl = defaultTargetPlatform == TargetPlatform.iOS | ||
| ? AppConstants.appStoreUrl | ||
| : AppConstants.googlePlayUrl; | ||
|
|
||
| if (storeUrl.isNotEmpty) { | ||
| await launchExternalUrl(context, storeUrl); | ||
| } else { | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| const SnackBar( | ||
| content: Text('このプラットフォームではレビュー機能は未対応です'), | ||
| ), |
| onTap: () { | ||
| unawaited(_requestReview(context)); |
Summary
url_launcher/in_app_reviewパッケージを追加変更内容
アプリ情報セクション
v1.0.0形式で表示showLicensePage()でFlutter標準ライセンス画面を表示法的情報セクション
Icons.open_in_new)Icons.open_in_new)サポートセクション
in_app_review→ フォールバックでストアURLその他
lib/constants/app_constants.dart— URL・メールアドレスの定数を一元管理lib/utils/url_launcher_helper.dart— URL起動・メール起動ヘルパー(エラー時SnackBar表示)Test plan
fvm flutter analyze— lint違反なしfvm flutter test— 全7テスト通過Closes #40
🤖 Generated with Claude Code